#include "DecoderGC.h"
#include <cassert>
#include <zxing/FormatException.h>
#include <zxing/common/reedsolomon/ReedSolomonDecoder.h>
#include <zxing/common/reedsolomon/GF256.h>
#include <zxing/common/reedsolomon/ReedSolomonException.h>
#include <cmath>

#ifdef DEBUG
#include <iostream>
#endif

namespace newcode {
namespace decoder {

using namespace zxing;

const float Decoder::ERROR_CORECTION_LEVELS[] = {0.2, 0.4, 0.45, 0.25};

unsigned Decoder::select2EqualFrom3(unsigned p1, unsigned p2, unsigned p3)
{
    if (p1 == p2)
    {
        return p1;
    }
    else if (p1 == p3)
    {
        return p3;
    }
    else if (p2 == p3)
    {
        return p2;
    }
    else
    {
        return -1;
    }
}

Ref<DecoderResult> Decoder::decode(Ref<ByteMatrix> bytes)
{
    if (bytes->getX() != bytes->getY())
    {
        throw FormatException("Data matrix must be square");
    }

    if (bytes->getX() < 14)
    {
        throw FormatException("Matrix size too small");
    }

    if((bytes->getX() - 2) % 4 != 0 )
    {
        throw FormatException("Size of matrix must be devidable by 4");
    }

    theBytes = bytes;
    assert(sizeof(long) >= 4);
    decodeFormatElements();
    ArrayRef<unsigned char> rawBytes = decodeBytes();
    Ref<String> text;
    if (formatIsString)
    {
        text = Ref<String>(new String(string((const char*)&rawBytes[0],
            rawBytes.size())));
    }
    else
    {
        text = Ref<String>(NULL);
    }

    return Ref<DecoderResult>(new DecoderResult(rawBytes, text));
}

ArrayRef<unsigned char> Decoder::filterElement(
        ArrayRef<unsigned char> fromImage, encoder::EncodeMode mode)
{
    if (mode != encoder::ENCODE_MODE_3_LEVELS)
    {
        int bytesInElement = (int) mode + 1;
        ArrayRef<unsigned char> result(new unsigned char[bytesInElement],
            bytesInElement);
        long buffer = 0;
        float coreValue = 255.f / ((1 << bytesInElement) - 1);

        for (int i = 0; i < 8; i++)
        {
            buffer <<= bytesInElement;
            //add 0.5 to round
            buffer += (int)((255.f - fromImage[i]) / coreValue + 0.5);
        }

        for (int i = bytesInElement - 1; i >= 0; i--)
        {
            result[i] = (buffer & 0xFF) ^ MASK;
            buffer >>= 8;
        }

        return result;
    }
    else
    {
        ArrayRef<unsigned char> result(3);
        vector<int> digits(16);
        for (int i = 0; i < 16; i++)
        {
            digits[i] = (int)((255.f - fromImage[i]) / 127.5f + 0.5f);
        }
        int buf = 0;
        int power = 1;
        for (int i = 15; i >= 0; i--)
        {
            buf += power * digits[i];
            power *= 3;
        }

        for (int i = 2; i >= 0; i--)
        {
            result[i] = (buf & 0xFF) ^ MASK;
            buf >>= 8;
        }
        return result;
    }
}

void Decoder::decodeFormatElements()
{
    dimension = theBytes->getX() - 2;

    int size1 = selectFormatByte(1, 1) + (selectFormatByte(3, 1) << 8);
    int size2 = (selectFormatByte(1, dimension - 3) << 8) +
        selectFormatByte(1, dimension - 7);
    int size3 = (selectFormatByte(dimension - 1, dimension - 3) << 8) +
        selectFormatByte(dimension - 1, dimension - 7);

#   ifdef DEBUG
    std::cout << "decoded sizes " << dec <<size1 << ", " << size2 << ", " <<
        size3 << endl;
#   endif

    unsigned size = select2EqualFrom3(size1, size2, size3);
    if (size == (unsigned)-1 || size > 0xFFFF)
    {
        throw ReaderException("Could not detect size of code");
    }
    else
    {
        formatSize = (unsigned) size;
    }

    unsigned char coreByte1 = selectFormatByte(5, 1);
    unsigned char coreByte2 = selectFormatByte(3, dimension - 3);
    unsigned char coreByte3 = selectFormatByte(dimension - 3, dimension - 3);

#   ifdef DEBUG
    std::cout << "decoded core byte " << hex << (int)coreByte1 << ", " <<
        (int)coreByte2 << ", " << (int) coreByte3 << endl;
#   endif

    formatIsString = (bool)select2EqualFrom3(coreByte1 & 1,
        coreByte2 & 1, coreByte3 & 1);
    coreByte1 >>= 6;
    coreByte2 >>= 6;
    coreByte3 >>= 6;
    unsigned mode = select2EqualFrom3(coreByte1 & 3, coreByte2 & 3,
        coreByte3 & 3);
    if (mode == (unsigned) -1)
    {
         throw ReaderException("Could not detect mode of code");
    }
    else
    {
        formatMode = (encoder::EncodeMode) mode;
    }
}

ArrayRef<unsigned char> Decoder::decodeBytes()

{
    int sizeToRead = sizeWithECB(formatSize);
    ArrayRef<unsigned char> bytesFromImage(sizeToRead);
    int currentPointer = 0;
    int hieght = dimension / 4;
    int weigh = dimension / 2;
    bool finished = false;
    bool isOdd = true;
    ArrayRef<unsigned char> elementsFromImage(
        formatMode != encoder::ENCODE_MODE_3_LEVELS? 8: 16);
    for(int w = 0; w < weigh && !finished; w++)
    {
        for(int h = 0; h < hieght && !finished; h++)
        {
            int absoluteH =  4 * h + 1;
            int absoluteW = 2 * w + 1;

            bool isTopLeftCorner = (h == 0 && w < 3);
            bool isBottom1 = (h == hieght - 2 &&
                (w == 0 || w == weigh - 1));
            bool isBottom2 = (h == hieght - 1 && (w < 2 || w > weigh - 3));

            if (!(isTopLeftCorner || isBottom1 || isBottom2))
            {
                ArrayRef<unsigned char> bytes;

                if(formatMode != encoder::ENCODE_MODE_3_LEVELS)
                {
                    getElementsFromImage(elementsFromImage,
                        absoluteW, absoluteH);
                    bytes = filterElement(elementsFromImage, formatMode);
                }
                else
                {
                    if (isOdd)
                    {
                        getElementsFromImage(elementsFromImage, absoluteW,
                            absoluteH, isOdd);
                        isOdd = false;
                        continue;
                    }
                    else
                    {
                        getElementsFromImage(elementsFromImage, absoluteW,
                            absoluteH, isOdd);
                        bytes = filterElement(elementsFromImage, formatMode);
                        isOdd = true;
                    }

                }
                for(size_t i = 0; i < bytes.size() && !finished; i++)
                {
                    bytesFromImage[currentPointer++] = bytes[i];
                    if (currentPointer == sizeToRead)
                    {
                        finished = true;
                    }
                }
            }
        }
    }

    if (!finished)
    {
        throw FormatException("Code size doesn't match to matrix");
    }

#   ifdef DEBUG
    cout << "data read: ";
    for (size_t i = 0; i < bytesFromImage.size(); i++)
    {
        cout << (char) bytesFromImage[i] << "(" << hex <<
            (int) bytesFromImage[i] << ") ";
    }
    cout << endl;
#   endif

    return correctErrors(bytesFromImage, formatSize);
}

void Decoder:: getElementsFromImage(ArrayRef<unsigned char> elementsFromImage,
    int x, int y, bool isOdd)
{
    if (formatMode != encoder::ENCODE_MODE_3_LEVELS || isOdd)
    {
        for (int i = 0; i < 4; i++)
        {
            elementsFromImage[2 * i] = (*theBytes)(x, y + i);
            elementsFromImage[2 * i + 1] = (*theBytes)(x + 1, y + i);
        }
    }
    else
    {
        for (int i = 0; i < 4; i++)
        {
            elementsFromImage[2 * i + 8] = (*theBytes)(x, y + i);
            elementsFromImage[2 * i + 9] = (*theBytes)(x + 1, y + i);
        }
    }
}

int Decoder::sizeWithECB(int size)
{
    return 2 * ceil(ERROR_CORECTION_LEVELS[(int)formatMode] * size) + size;
}

unsigned char Decoder::selectFormatByte(int x, int y)
{
    ArrayRef<unsigned char> fromImage(8);
    getElementsFromImage(fromImage, x, y);
    return filterElement(fromImage, encoder::ENCODE_MODE_2_LEVELS)[0];
}

ArrayRef<unsigned char> Decoder::correctErrors(
    ArrayRef<unsigned char> codewordBytes, int numDataCodewords)
{
    int numCodewords = codewordBytes->size();
    ArrayRef<int> codewordInts(numCodewords);
    ArrayRef<unsigned char> result(numDataCodewords);
    for (int i = 0; i < numCodewords; i++)
    {
        codewordInts[i] = codewordBytes[i] & 0xff;
    }
    int numECCodewords = numCodewords - numDataCodewords;
    ReedSolomonDecoder rsDecoder(GF256::QR_CODE_FIELD);
    try
    {
        rsDecoder.decode(codewordInts, numECCodewords);
    }
    catch (ReedSolomonException const& ex)
    {
        ReaderException rex(ex.what());
        throw rex;
    }
    // Copy back into array of bytes -- only need to worry about the bytes that
    // were data. We don't care about errors in the error-correction codewords.
    for (int i = 0; i < numDataCodewords; i++)
    {
        result[i] = (unsigned char)codewordInts[i];
    }
    return result;
}

}
}

